import math
import numpy as np
import pandas as pd

import statistics


# 计算了与最优熵的比值
def get_vge(niches):
    return f(abs((H_t(niches) - H_b(sum(niches))) / H_b(sum(niches))))
# 当前时刻的最优价值熵Hb
def H_b(N):
    if N > 0:
        return math.log2(math.sqrt(N))
    else:
        return 1


# 当前时刻的价值熵Ht
# niches:每个生态位上的数目组成的数组
def H_t(niches):
    H_t = 0
    for n_i in niches:
        if sum(niches) != 0:
            p_i = n_i / sum(niches)
            if p_i > 0:
                H_t += -p_i * math.log2(p_i)
    return H_t

class calUtility:
    def __init__(self,  agent_matrix = dict()):
        self.agent_matrix = agent_matrix
        self.system_utility_list = dict(system_utility = dict(),
                                        entropy = dict(),
                                        productivity = dict())
        self.avg_utility_list = dict()

    def set_agent_matrix(self, agent_matrix):
        self.agent_matrix = agent_matrix

    def update_agent_matrix(self, data, t):
        if  str(t) not in self.agent_matrix:
           self.agent_matrix[str(t)] = data
        else:
            self.agent_matrix[str(t)].update(data)
    
    def update_utility(self, t=0):
        self.avg_utility_list[t] = self.get_avg_utility(t)
        self.system_utility_list["system_utility"][t], self.system_utility_list["entropy"][t], self.system_utility_list["productivity"][t] = self.system_utility(t)

    # 计算时，首先根据指定的生态位划分标准，利用常用的排序算法，对所有个体智能进行排序，将个体纳入合适的生态位，从而得到生态位数m
    def get_niche(self, t):
        assert str(t) in self.agent_matrix.keys()
        # 生态位置的划分标准
        # 计算每个生态位上的数目
        niches = [0, 0, 0, 0, 0, 0]
        self.get_zhineng(t)
        # print('个体智能:', self.get_zhineng(t).values())
        for v in self.agent_zhineng_matrix.values():
            if v < -1:
                niches[0] += 1
            elif -1 <= v < 0:
                niches[1] += 1
            elif 0 <= v < 1:
                niches[2] += 1
            elif 1 <= v < 2:
                niches[3] += 1
            elif 2 <= v < 3:
                niches[4] += 1
            elif 3 <= v:
                niches[5] += 1
        # 得到系统当前生态位数,当前系统生态位的分布情况
        self.niches = niches
        # print('生态位', niches)
        return sum(i > 0 for i in niches), niches

    # 计算所有个体的个体智能，存入self.agent_zhineng_matrix
    def get_zhineng(self, t):
        assert str(t) in self.agent_matrix.keys()

        self.agent_zhineng_matrix = dict()
        avg_utility = self.get_avg_utility(t)
        # 将个体智能值存入矩阵
        for idx in self.agent_utility_matrix.keys():
            if avg_utility != 0:
                self.agent_zhineng_matrix.update({str(idx): (self.agent_utility_matrix[str(idx)] / avg_utility)})
        return self.agent_zhineng_matrix

    # 计算所有个体的平均个体效能
    def get_avg_utility(self, t):
        assert str(t) in self.agent_matrix.keys()

        self.agent_utility_matrix = dict()
        # ids = np.array([agentId for agentId in self.agent_matrix.get(str(self.schedule.steps)).keys()])
        # 这里用self.all_agents，而不是match_agent
        ids = np.array([int(idx) for idx in self.agent_matrix[str(t)].keys()])
        for idx in ids:
            self_utility = self.get_self_utility(idx,t, gamma=1)
            # print("计算的该Agent的效能值", self_utility)
            self.agent_utility_matrix.update({str(idx): self_utility})
        # 计算所有个体效能平均值
        # print("match_agent",self.match_agent)
        N = len(self.agent_utility_matrix.keys())
        # print("N", N)
        V = sum(self.agent_utility_matrix.values())
        # print("V", V)
        return V / N

    # 获取个体效能
    # 为每个个体存一个矩阵，记录当前时间t的reward和cost
    def get_self_utility(self, agent_id, t, gamma=1):
        assert str(t) in self.agent_matrix.keys()
        self_utility = 0
        if t != 0:
            for i in range(t + 1):
                if self.agent_matrix.get(str(i)).get(str(agent_id)):  # 如果历史时间段有这个agent的数据，才加
                    self_utility += (gamma ** (t - i)) * (self.agent_matrix.get(str(i)).get(str(agent_id))[0] -
                                                        self.agent_matrix.get(str(i)).get(str(agent_id))[1])

            return self_utility / t
        else:
            return 0
            
    # 获得系统效能
    def system_utility(self, t=0):
        assert str(t) in self.agent_matrix.keys()

        # print('个体效能' ,self.agent_utility_matrix.values())
        _, niches = self.get_niche(t)
        _ = self.get_avg_utility(t)
        entroy =  get_vge(niches)
        productivity = sum(self.agent_utility_matrix.values())
        return entroy * productivity, entroy, productivity


# 边际递减函数,eta 可调节
def crra(x, eta=0.5):
    return (x ** (1 - eta) - 1) / (1 - eta)


# 基尼系数
def get_gini(endowments):
    """Returns the normalized Gini index describing the distribution of endowments.

    https://en.wikipedia.org/wiki/Gini_coefficient

    Args:
        endowments (ndarray): The array of endowments for each of the agents in the
            simulated economy.

    Returns:
        Normalized Gini index for the distribution of endowments (float). A value of 1
            indicates everything belongs to 1 agent (perfect inequality), whereas a
            value of 0 indicates all agents have equal endowments (perfect equality).

    Note:
        Uses a slightly different method depending on the number of agents. For fewer
        agents (<30), uses an exact but slow method. Switches to using a much faster
        method for more agents, where both methods produce approximately equivalent
        results.
    """
    n_agents = len(endowments)

    if n_agents < 30:  # Slower. Accurate for all n.
        diff_ij = np.abs(
            endowments.reshape((n_agents, 1)) - endowments.reshape((1, n_agents))
        )
        diff = np.sum(diff_ij)
        norm = 2 * n_agents * endowments.sum(axis=0)
        unscaled_gini = diff / (norm + 1e-10)
        gini = unscaled_gini / ((n_agents - 1) / n_agents)
        return gini

    # Much faster. Slightly overestimated for low n.
    s_endows = np.sort(endowments)
    return 1 - (2 / (n_agents + 1)) * np.sum(
        np.cumsum(s_endows) / (np.sum(s_endows) + 1e-10)
    )


# 公平性
def get_equality(endowments):
    """Returns the complement of the normalized Gini index (equality = 1 - Gini).

    Args:
        endowments (ndarray): The array of endowments for each of the agents in the
            simulated economy.

    Returns:
        Normalized equality index for the distribution of endowments (float). A value
            of 0 indicates everything belongs to 1 agent (perfect inequality),
            whereas a value of 1 indicates all agents have equal endowments (perfect
            equality).
    """
    return 1 - get_gini(endowments)


# 生产力
def get_productivity(coin_endowments):
    """Returns the total coin inside the simulated economy.

    Args:
        coin_endowments (ndarray): The array of coin endowments for each of the
            agents in the simulated economy.

    Returns:
        Total coin endowment (float).
    """
    return np.sum(coin_endowments)



    # return agent_matrix.get(str(t)).get(str(agent_id))[0] - agent_matrix.get(str(t)).get(str(agent_id))[1]


# 指数衰减函数=>修改为e*(1-x)
def f(x):
    return math.exp(1-x)




# 计算需求侧效能
# 设定一个social_matrix，用于存系统每个时间上新增的order和以及完成的order
# 设定一个agent_matrix,用于存每个agent每个时间上的reward和cost
def get_dutility(social_matrix, t, gamma=0.8):
    dutility = 0
    for i in range(int(t + 1)):
        if social_matrix.get(str(i))[0] != 0:
            dutility += (gamma ** (t - i)) * (social_matrix.get(str(i))[1] / social_matrix.get(str(i))[0])
    return dutility



def weighted_amortized_fairness(coin_endowment, total_labor):
    """
    weighted amortized fairness
    Args:
        coin_endowment (ndarray): The income of a agent in the simulated system from Time 0 to T.
        total_labor (ndarray): The work hours of a agent in the simulated system from Time 0 to T.

    Returns:
         weighted amortized fairness (float).
    """
    # print(sum(coin_endowment))

    time_step = math.ceil(len(coin_endowment) / 60)
    normal_income = 0
    for i in range(time_step):
        if i!= time_step-1:
            equality_weight = statistics.median(coin_endowment[i*60:(i+1)*60])
            normal_income += sum(coin_endowment[i*60:(i+1)*60])/(equality_weight + 0.1)
        else:
            equality_weight = statistics.median(coin_endowment[i*60:])
            normal_income += sum(coin_endowment[i*60:])/(equality_weight + 0.1)
    if normal_income <= 0:
        return 1e-10
    
    return normal_income/(sum(total_labor) + 0.1)


def temporal_earnings_fairness(coin_endowments, total_labors):
    """
    temporal earnings fairness
    Args:
        coin_endowments (ndarray): The income of the agents in the simulated system from Time 0 to T.
        total_labors (ndarray): The work hours of the agents in the simulated system from Time 0 to T.


    Returns:
         temporal earnings fairness (float).
    """
    fairness = [ weighted_amortized_fairness(coin_endowments[i], total_labors[i]) for i in range(len(coin_endowments))]
    assert np.all(np.array(fairness) >= 0)
    print(fairness)
    return -sum([np.log(f/max(fairness)) for f in fairness]) # / len(fairness)

def temporal_earnings_unfairnesses(coin_endowments, total_labors):
    """
    temporal earnings fairness
    Args:
        coin_endowments (ndarray): The income of the agents in the simulated system from Time 0 to T.
        total_labors (ndarray): The work hours of the agents in the simulated system from Time 0 to T.


    Returns:
        temporal earnings unfairnessed (list).
    """
    fairness = [ weighted_amortized_fairness(coin_endowments[i], total_labors[i]) for i in range(len(coin_endowments))]
    assert np.all(np.array(fairness) >= 0)
    #print( max(fairness), fairness.index( max(fairness)), fairness.count(min(fairness)))
    return [np.log(f/max(fairness)) for f in fairness]

# 基尼系数
def get_gini(endowments):
    """Returns the normalized Gini index describing the distribution of endowments.

    https://en.wikipedia.org/wiki/Gini_coefficient

    Args:
        endowments (ndarray): The array of endowments for each of the agents in the
            simulated economy.

    Returns:
        Normalized Gini index for the distribution of endowments (float). A value of 1
            indicates everything belongs to 1 agent (perfect inequality), whereas a
            value of 0 indicates all agents have equal endowments (perfect equality).

    Note:
        Uses a slightly different method depending on the number of agents. For fewer
        agents (<30), uses an exact but slow method. Switches to using a much faster
        method for more agents, where both methods produce approximately equivalent
        results.
    """
    n_agents = len(endowments)

    if n_agents < 30:  # Slower. Accurate for all n.
        diff_ij = np.abs(
            endowments.reshape((n_agents, 1)) - endowments.reshape((1, n_agents))
        )
        diff = np.sum(diff_ij)
        norm = 2 * n_agents * endowments.sum(axis=0)
        unscaled_gini = diff / (norm + 1e-10)
        gini = unscaled_gini / ((n_agents - 1) / n_agents)
        return gini

    # Much faster. Slightly overestimated for low n.
    s_endows = np.sort(endowments)
    return 1 - (2 / (n_agents + 1)) * np.sum(
        np.cumsum(s_endows) / (np.sum(s_endows) + 1e-10)
    )